package com.app.backend.common.aspect;


import com.alibaba.fastjson.JSONObject;
import com.app.backend.common.annotation.InvokeLog;
import com.app.backend.common.base.BaseEntity;
import com.app.backend.common.entity.Log;
import com.app.backend.common.service.LogService;
import com.app.backend.common.utils.RequestContextHandler;
import com.app.backend.common.utils.RequestIpUtil;
import com.app.backend.common.utils.ThreadPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.StopWatch;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.lang.reflect.Method;

/**
 * @program: InvokeLogAspect
 * @description: 日志切面处理类
 * @author: Zhun.Xiao
 * @create: 2018-10-29 17:01
 **/

@Slf4j
@Aspect
@Component
public class InvokeLogAspect {


    private static final String MSG = "\n --请求--\n --方法：{}\n --描述：{}\n --位置：{}\n --参数：{}\n --返回：{}\n --耗时：{} ms";
    /**
     * 服务响应超过2秒打印警告日志
     */
    private static final int DEFAULT_TIME_LIMIT = 2000;

    @Autowired
    private LogService logService;


    @Autowired(required = false)
    private HttpServletRequest request;

    /**
     * @param name            操作名称
     * @param description     描述
     * @param printReturn     是否打印响应
     * @param joinPoint       位置
     * @param returnObj       响应
     * @param totalTimeMillis 耗时ms
     */
    protected void printLogMsg(String name, String description, boolean printReturn, JoinPoint joinPoint, String params, Object returnObj, long totalTimeMillis) {
        if (totalTimeMillis < DEFAULT_TIME_LIMIT) {
            log.info(MSG, new Object[]{name, description, joinPoint.getStaticPart(), params, getPrintMsg(printReturn, returnObj), totalTimeMillis});
        } else {
            log.warn(MSG, new Object[]{name, description, joinPoint.getStaticPart(), params, getPrintMsg(printReturn, returnObj), totalTimeMillis});
        }
    }

    protected String getPrintMsg(boolean printReturn, Object returnObj) {
        return printReturn ?
                ((returnObj != null) ? JSONObject.toJSONString(returnObj) : "null")
                : "[printReturn = false]";
    }

    protected Object[] argsDemote(Object[] args) {
        if (args == null || args.length == 0) {
            return new Object[]{};
        }
        Object[] params = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            if (arg instanceof ServletRequest || arg instanceof ServletResponse
                    || arg instanceof ModelMap || arg instanceof Model
                    || arg instanceof InputStreamSource || arg instanceof File || arg instanceof BaseEntity) {
                params[i] = args[i];
            } else {
                params[i] = args.toString();
            }
        }
        return params;
    }

    protected void handleLog(ProceedingJoinPoint joinPoint, String params, Object returnObj, long costTime) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        InvokeLog invokeLog = method.getAnnotation(InvokeLog.class);

        // HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = RequestIpUtil.getIpAddr(request);
        String methodName = joinPoint.getSignature().getName();
        String requestUrl = request.getRequestURI();
        String username = RequestContextHandler.getCurrentUserName();

        Log log = new Log();
        log.setIp(ip);
        log.setRequestParam(params);
        log.setCostTime((int) costTime);
        log.setLogType(invokeLog.type().ordinal());
        log.setName(methodName);
        log.setRequestUrl(requestUrl);
        log.setUsername(username);

        if (invokeLog.persistence()) {
            ThreadPoolUtil.getPool().execute(() -> {
                logService.save(log);
            });
        }

        printLogMsg(methodName, invokeLog.description(), invokeLog.printReturn(), joinPoint, params, returnObj, costTime);
    }


    // 定义切点Pointcut
    @Pointcut("@annotation(com.app.backend.common.annotation.InvokeLog)")
    public void executePointcut() {
    }


    @Around("executePointcut()")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        String params = JSONObject.toJSONString(args);
        if (log.isDebugEnabled() || log.isWarnEnabled()) {
            StopWatch clock = new StopWatch();
            clock.start();
            Object returnObj = null;
            try {
                return returnObj = joinPoint.proceed(args);
            } catch (Exception e) {
                throw e;
            } finally {
                clock.stop();
                long totalTimeMillis = clock.getTotalTimeMillis();
                handleLog(joinPoint, params, returnObj, totalTimeMillis);
            }
        } else {
            return joinPoint.proceed(args);
        }
    }


}
